<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <title>查重功能与过程验证</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/json-formatter-js@2.3.4/dist/json-formatter.min.css">
    <script src="https://cdn.jsdelivr.net/npm/json-formatter-js@2.3.4/dist/json-formatter.cjs.js"></script>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            line-height: 1.6;
        }

        .container {
            max-width: 1000px;
            margin: auto;
            padding: 20px;
            background: #f9f9f9;
            border-radius: 8px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }

        .section {
            margin-bottom: 30px;
            padding: 20px;
            background: #fff;
            border: 1px solid #ddd;
            border-radius: 5px;
        }

        h1,
        h2,
        h3 {
            color: #333;
        }

        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }

        input[type="file"],
        textarea,
        input[type="text"] {
            width: calc(100% - 22px);
            padding: 10px;
            margin-bottom: 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
        }

        textarea {
            min-height: 100px;
            resize: vertical;
        }

        button {
            background-color: #007bff;
            color: white;
            padding: 10px 15px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            margin-right: 10px;
        }

        button:hover {
            background-color: #0056b3;
        }

        .result-box {
            margin-top: 15px;
            padding: 15px;
            background: #e9ecef;
            border: 1px solid #ced4da;
            border-radius: 4px;
            max-height: 400px;
            overflow: auto;
            white-space: pre-wrap;
            word-wrap: break-word;
        }

        .spinner {
            display: none;
            /* ... (spinner样式同前) ... */
        }

        .highlight {
            background-color: yellow;
        }

        .segment {
            border: 1px dashed #aaa;
            margin: 5px 0;
            padding: 5px;
            background-color: #f0f0f0;
        }

        .segment .text {
            font-family: monospace;
        }

        .segment .pos {
            font-size: 0.8em;
            color: #555;
        }

        .tabs {
            display: flex;
            margin-bottom: 15px;
            border-bottom: 1px solid #ccc;
        }

        .tab-button {
            padding: 10px 15px;
            cursor: pointer;
            border: none;
            background-color: transparent;
            font-size: 16px;
            border-bottom: 3px solid transparent;
            margin-right: 5px;
            color: #777; /* <<< MODIFIED HERE: Default color for inactive tabs */
        }

        .tab-button.active {
            border-bottom-color: #007bff;
            font-weight: bold;
            color: #007bff; /* Active tab color remains blue */
        }

        .tab-content {
            display: none;
        }

        .tab-content.active {
            display: block;
        }

        .text-comparison-area {
            display: flex;
            justify-content: space-between;
            margin-bottom: 15px;
        }

        .text-comparison-area textarea {
            width: 48%;
            min-height: 150px;
        }

        .highlighted-text-container {
            border: 1px solid #eee;
            padding: 10px;
            margin-top: 10px;
            background: #f8f8f8;
            max-height: 300px;
            overflow-y: auto;
            white-space: pre-wrap;
            font-family: monospace;
        }

        .highlight-literal {
            background-color: rgba(255, 0, 0, 0.2);
        }

        .highlight-semantic {
            background-color: rgba(255, 165, 0, 0.2);
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>查重功能与过程验证平台</h1>
        <label for="api_base_url_plagiarism">API 基础 URL (查重服务):</label>
        <input type="text" id="api_base_url_plagiarism" value="http://127.0.0.1:5055/plagiarism">

        <!-- Tabbed Interface -->
        <div class="tabs">
            <button class="tab-button active" onclick="openTab(event, 'tabFullCheck')">完整查重</button>
            <button class="tab-button" onclick="openTab(event, 'tabPreprocess')">文本预处理</button>
            <button class="tab-button" onclick="openTab(event, 'tabCandidates')">候选集生成</button>
            <button class="tab-button" onclick="openTab(event, 'tabCompareTexts')">双文本比较</button>
            <button class="tab-button" onclick="openTab(event, 'tabVectorExplore')">语义向量探索</button>
        </div>

        <!-- Tab Content: 完整查重 -->
        <div id="tabFullCheck" class="tab-content active">
            <h2>1. 完整文件查重</h2>
            <label for="check_file_input">选择待查重文件 (txt, pdf, docx):</label>
            <input type="file" id="check_file_input" accept=".txt,.pdf,.docx">
            <button onclick="runFullCheck()">开始查重</button>
            <div class="spinner" id="spinner_full_check"></div>
            <h3>查重结果 (CoreResultData JSON):</h3>
            <div class="result-box" id="result_full_check">点击“开始查重”后，结果将在此显示...</div>
            <div id="report_link_container" style="margin-top:15px;"></div>
        </div>

        <!-- Tab Content: 文本预处理 -->
        <div id="tabPreprocess" class="tab-content">
            <h2 id="preprocess_title">2. 文本预处理效果</h2>
            <label for="preprocess_file_input">上传文件进行预处理:</label>
            <input type="file" id="preprocess_file_input" accept=".txt,.pdf,.docx">
            <label for="preprocess_text_input">或者输入文本进行预处理:</label>
            <textarea id="preprocess_text_input" placeholder="在此输入文本..."></textarea>
            <button onclick="runPreprocessing()">执行预处理</button>
            <div class="spinner" id="spinner_preprocess"></div>
            <div id="result_preprocess_output">
                <h4>统计信息:</h4>
                <pre id="preprocess_stats" class="result-box"></pre>
                <h4>清洗后文本预览 (点击分段/分句可高亮):</h4>
                <div id="preprocess_cleaned_preview" class="highlighted-text-container" data-original-text=""></div>
                <h4>段落预览 (前20段):</h4>
                <div id="preprocess_paragraphs_preview" class="result-box segments-container"></div>
                <h4>句子预览 (前50句):</h4>
                <div id="preprocess_sentences_preview" class="result-box segments-container"></div>
            </div>
        </div>

        <!-- Tab Content: 候选集生成 -->
        <div id="tabCandidates" class="tab-content">
            <h2>3. 候选集生成</h2>
            <label for="candidate_file_input">上传文件以生成候选集:</label>
            <input type="file" id="candidate_file_input" accept=".txt,.pdf,.docx">
            <button onclick="runCandidateGeneration()">生成候选集</button>
            <div class="spinner" id="spinner_candidates"></div>
            <h3>候选集结果:</h3>
            <div class="result-box" id="result_candidates">点击按钮后显示...</div>
        </div>

        <!-- Tab Content: 双文本比较 -->
        <div id="tabCompareTexts" class="tab-content">
            <h2>4. 双文本直接比较</h2>
            <div class="text-comparison-area">
                <textarea id="compare_text_1" placeholder="输入文本1..."></textarea>
                <textarea id="compare_text_2" placeholder="输入文本2..."></textarea>
            </div>
            <label>选择比较类型:
                <input type="checkbox" id="compare_opt_literal" checked> 字面
                <input type="checkbox" id="compare_opt_semantic" checked> 语义
                <input type="checkbox" id="compare_opt_ngram" checked> N-gram
            </label>
            <button onclick="runTextComparison()">比较文本</button>
            <div class="spinner" id="spinner_compare_texts"></div>
            <h3>比较结果:</h3>
            <div class="result-box" id="result_compare_texts">输入文本并点击比较...</div>
            <h4>文本1高亮 (字面匹配):</h4>
            <div id="compare_text_1_highlight" class="highlighted-text-container"></div>
            <h4>文本2高亮 (字面匹配):</h4>
            <div id="compare_text_2_highlight" class="highlighted-text-container"></div>
        </div>

        <!-- Tab Content: 语义向量探索 -->
        <div id="tabVectorExplore" class="tab-content">
            <h2>5. 语义向量探索</h2>
            <label for="vector_text_input">输入文本获取语义向量:</label>
            <textarea id="vector_text_input" placeholder="输入文本..."></textarea>
            <button onclick="runVectorExploration()">获取向量</button>
            <div class="spinner" id="spinner_vector_explore"></div>
            <h3>文本向量 (部分预览):</h3>
            <div class="result-box" id="result_vector_explore">输入文本并点击获取...</div>
        </div>

    </div>

    <script>
        // --- Tab切换逻辑 ---
        function openTab(evt, tabName) {
            let i, tabcontent, tabbuttons;
            tabcontent = document.getElementsByClassName("tab-content");
            for (i = 0; i < tabcontent.length; i++) {
                tabcontent[i].style.display = "none";
                tabcontent[i].classList.remove("active");
            }
            tabbuttons = document.getElementsByClassName("tab-button");
            for (i = 0; i < tabbuttons.length; i++) {
                tabbuttons[i].classList.remove("active");
            }
            document.getElementById(tabName).style.display = "block";
            document.getElementById(tabName).classList.add("active");
            evt.currentTarget.classList.add("active");
        }
        // 默认打开第一个tab
        document.addEventListener('DOMContentLoaded', () => {
            document.querySelector('.tab-button.active').click();
        });

        const plagiarismApiBaseUrl = () => document.getElementById('api_base_url_plagiarism').value.trim();

        function showSpinner(spinnerId, show) {
            document.getElementById(spinnerId).style.display = show ? 'block' : 'none';
        }
        
        function escapeHtml(unsafe) {
            if (typeof unsafe !== 'string') return '';
            return String(unsafe)
            .replace(/&/g, "&amp;")
            .replace(/</g, "&lt;")
            .replace(/>/g, "&gt;")
            .replace(/"/g, "&quot;")
            .replace(/'/g, "&#39;");
        }

        function displayJsonResult(elementId, jsonData, messageIfEmpty = "无数据或结果为空。") {
            const container = document.getElementById(elementId);
            container.innerHTML = ''; // Clear previous
            if (jsonData && (typeof jsonData === 'object' && Object.keys(jsonData).length > 0 || typeof jsonData !== 'object' && jsonData) ) {
                try {
                    const formatter = new JSONFormatter(jsonData, 2, { 
                        theme: 'default', 
                        hoverPreviewEnabled: true,
                        hoverPreviewArrayCount: 100,
                        hoverPreviewFieldCount: 5,
                        animateOpen: true,
                        animateClose: true,
                        useToJSON: true
                    });
                    container.appendChild(formatter.render());
                } catch (e) {
                    const pre = document.createElement('pre');
                    pre.textContent = JSON.stringify(jsonData, null, 2);
                    container.appendChild(pre);
                    console.warn(`JSONFormatter for ${elementId} failed, falling back to preformatted text. Error: ${e}, Data:`, jsonData);
                }
            } else {
                const p = document.createElement('p');
                p.textContent = messageIfEmpty;
                container.appendChild(p);
            }
        }
        
        async function runFullCheck() {
            const fileInput = document.getElementById('check_file_input');
            const file = fileInput.files[0];
            const resultBox = document.getElementById('result_full_check');
            const spinnerId = 'spinner_full_check';
            const reportLinkContainer = document.getElementById('report_link_container');
            if (reportLinkContainer) reportLinkContainer.innerHTML = '';

            if (!file) { alert("请选择一个文件。"); return; }
            showSpinner(spinnerId, true);
            displayJsonResult('result_full_check', null, "正在查重，请稍候...");


            const formData = new FormData();
            formData.append('file', file);

            try {
                const response = await fetch(`${plagiarismApiBaseUrl()}/check_file`, { method: 'POST', body: formData });
                const data = await response.json();

                if (data.success && data.task_id && data.report_data) {
                    displayJsonResult('result_full_check', data.report_data, "查重完成，但未返回详细数据。");
                    if (reportLinkContainer) {
                        const reportUrl = `/plagiarism_static/report_template.html?task_id=${data.task_id}`;
                        const viewReportButton = document.createElement('button');
                        viewReportButton.textContent = `查看 "${escapeHtml(file.name)}" 的报告 (ID: ${data.task_id})`;
                        viewReportButton.onclick = function () { window.open(reportUrl, '_blank'); };
                        viewReportButton.style.marginTop = "10px";
                        viewReportButton.style.backgroundColor = "#28a745";
                        viewReportButton.onmouseover = function () { this.style.backgroundColor = "#218838"; };
                        viewReportButton.onmouseout = function () { this.style.backgroundColor = "#28a745"; };
                        reportLinkContainer.appendChild(viewReportButton);
                    }
                } else {
                    let errorMessage = `查重失败: ${data.error || "未知错误"}`;
                    if (data.data && data.data.error) errorMessage += ` (详情: ${data.data.error})`;
                    displayJsonResult('result_full_check', { error: errorMessage, details: data.data || null });
                }
            } catch (error) {
                console.error("完整查重请求失败:", error);
                displayJsonResult('result_full_check', { error: `请求错误: ${error.message}` });
            } finally {
                showSpinner(spinnerId, false);
            }
        }

        function displaySegments(containerId, segments, cleanedPreviewElementId, highlightClass = 'highlight') {
            const container = document.getElementById(containerId);
            container.innerHTML = '';
            const cleanedPreviewEl = document.getElementById(cleanedPreviewElementId);

            if (segments && segments.length > 0) {
                segments.forEach((seg, index) => {
                    const div = document.createElement('div');
                    div.classList.add('segment');
                    const textP = document.createElement('p');
                    textP.classList.add('text');
                    textP.textContent = `[${index + 1}] ${escapeHtml(seg.text)}`;
                    const posP = document.createElement('p');
                    posP.classList.add('pos');
                    posP.textContent = `(位置: ${seg.start}-${seg.end}, 长度: ${seg.text.length})`;

                    if (cleanedPreviewEl) {
                        div.style.cursor = 'pointer';
                        div.title = "点击高亮原文对应片段";
                        div.onclick = () => {
                            const originalText = cleanedPreviewEl.getAttribute('data-original-text');
                            if (!originalText) return;

                            // 清除之前所有同类高亮
                            let currentHTML = cleanedPreviewEl.innerHTML.replace(new RegExp(`<span class="${highlightClass}">(.*?)</span>`, 'g'), '$1');
                            cleanedPreviewEl.innerHTML = currentHTML; // 更新DOM以移除旧span

                            const start = seg.start;
                            const end = seg.end;
                            
                            if (start >= 0 && end <= originalText.length && start < end) {
                                const before = escapeHtml(originalText.substring(0, start));
                                const highlighted = escapeHtml(originalText.substring(start, end));
                                const after = escapeHtml(originalText.substring(end));
                                
                                cleanedPreviewEl.innerHTML = before.replace(/\n/g, '<br>') +
                                    `<span class="${highlightClass}">${highlighted.replace(/\n/g, '<br>')}</span>` +
                                    after.replace(/\n/g, '<br>');
                            } else {
                                // 如果无法高亮，至少恢复原文
                                cleanedPreviewEl.innerHTML = escapeHtml(originalText).replace(/\n/g, '<br>');
                            }
                        };
                    }
                    div.appendChild(textP);
                    div.appendChild(posP);
                    container.appendChild(div);
                });
            } else {
                container.textContent = "无分段数据。";
            }
        }

        async function runPreprocessing() {
            const fileInput = document.getElementById('preprocess_file_input');
            const textInput = document.getElementById('preprocess_text_input');
            const resultStatsEl = document.getElementById('preprocess_stats');
            const resultCleanedPreviewEl = document.getElementById('preprocess_cleaned_preview');
            const resultParagraphsEl = document.getElementById('preprocess_paragraphs_preview');
            const resultSentencesEl = document.getElementById('preprocess_sentences_preview');
            const spinnerId = 'spinner_preprocess';
            const preprocessTitleEl = document.getElementById('preprocess_title');

            const file = fileInput.files[0];
            const textContent = textInput.value.trim();

            if (!file && !textContent) { alert("请上传文件或输入文本。"); return; }
            showSpinner(spinnerId, true);
            
            displayJsonResult('preprocess_stats', null, "处理中...");
            resultCleanedPreviewEl.textContent = "处理中...";
            resultCleanedPreviewEl.removeAttribute('data-original-text');
            resultParagraphsEl.textContent = "处理中...";
            resultSentencesEl.textContent = "处理中...";
            preprocessTitleEl.textContent = "2. 文本预处理效果";


            const formData = new FormData();
            if (file) formData.append('file', file);
            else formData.append('text_content', textContent);

            try {
                const response = await fetch(`${plagiarismApiBaseUrl()}/preprocess_text`, { method: 'POST', body: formData });
                const data = await response.json();
                if (data.success && data.data) {
                    displayJsonResult('preprocess_stats', data.data.stats, "无统计信息。");
                    
                    const cleanedTextForHighlight = data.data.cleaned_text_preview || (data.data.full_cleaned_text_available ? "完整文本过长未预览" : "无清洗后文本");
                    resultCleanedPreviewEl.innerHTML = escapeHtml(cleanedTextForHighlight).replace(/\n/g, '<br>');
                    resultCleanedPreviewEl.setAttribute('data-original-text', data.data.cleaned_text_preview || ""); // 存储用于高亮的文本

                    displaySegments('preprocess_paragraphs_preview', data.data.paragraphs, 'preprocess_cleaned_preview');
                    displaySegments('preprocess_sentences_preview', data.data.sentences, 'preprocess_cleaned_preview');

                    // 更新标题
                    if (data.data.config_min_chunk_length !== undefined) {
                        preprocessTitleEl.textContent = `2. 文本预处理效果 (最小块长度配置: ${data.data.config_min_chunk_length})`;
                    }
                } else {
                    displayJsonResult('preprocess_stats', { error: data.error || "未知错误" });
                    resultCleanedPreviewEl.textContent = '预处理失败'; 
                    resultParagraphsEl.textContent = ''; 
                    resultSentencesEl.textContent = '';
                    preprocessTitleEl.textContent = "2. 文本预处理效果 (加载配置失败)";
                }
            } catch (error) {
                console.error("预处理请求失败:", error);
                displayJsonResult('preprocess_stats', { error: `请求错误: ${error.message}` });
                preprocessTitleEl.textContent = "2. 文本预处理效果 (请求失败)";
            } finally {
                showSpinner(spinnerId, false);
                fileInput.value = '';
            }
        }

        async function runCandidateGeneration() {
            const fileInput = document.getElementById('candidate_file_input');
            const file = fileInput.files[0];
            const resultBox = document.getElementById('result_candidates');
            const spinnerId = 'spinner_candidates';

            if (!file) { alert("请选择一个文件。"); return; }
            showSpinner(spinnerId, true);
            displayJsonResult('result_candidates', null, "正在生成候选集...");

            const formData = new FormData();
            formData.append('file', file);

            try {
                const response = await fetch(`${plagiarismApiBaseUrl()}/get_candidates`, { method: 'POST', body: formData });
                const data = await response.json();
                if (data.success) {
                    displayJsonResult('result_candidates', data.data, "未能生成候选集或结果为空。");
                } else {
                    displayJsonResult('result_candidates', { error: data.error || "未知错误" });
                }
            } catch (error) {
                console.error("候选集生成请求失败:", error);
                displayJsonResult('result_candidates', { error: `请求错误: ${error.message}` });
            } finally {
                showSpinner(spinnerId, false);
                fileInput.value = '';
            }
        }

        function highlightComparisonText(containerId, text, matches, isText1) {
            const container = document.getElementById(containerId);
            if (!text) {
                container.textContent = "无文本";
                return;
            }
            if (!matches || matches.length === 0) {
                container.innerHTML = escapeHtml(text).replace(/\n/g, '<br>');
                return;
            }

            let segments = [];
            let lastIndex = 0;

            // 对匹配项按起始位置排序，确保正确处理重叠（尽管这里简单覆盖）
            matches.sort((a, b) => (isText1 ? a[0] : a[1]) - (isText1 ? b[0] : b[1]));
            
            matches.forEach(match => {
                const start = isText1 ? match[0] : match[1];
                const length = match[2];
                const end = start + length;

                if (start >= 0 && end <= text.length && start < end) {
                    if (start > lastIndex) {
                        segments.push({ text: text.substring(lastIndex, start), highlighted: false });
                    }
                    segments.push({ text: text.substring(start, end), highlighted: true });
                    lastIndex = end;
                }
            });

            if (lastIndex < text.length) {
                segments.push({ text: text.substring(lastIndex), highlighted: false });
            }
            
            container.innerHTML = segments.map(seg => {
                const escapedText = escapeHtml(seg.text).replace(/\n/g, '<br>');
                return seg.highlighted ? `<span class="highlight-literal">${escapedText}</span>` : escapedText;
            }).join('');
        }


        async function runTextComparison() {
            const text1 = document.getElementById('compare_text_1').value;
            const text2 = document.getElementById('compare_text_2').value;
            const optLiteral = document.getElementById('compare_opt_literal').checked;
            const optSemantic = document.getElementById('compare_opt_semantic').checked;
            const optNgram = document.getElementById('compare_opt_ngram').checked;
            const resultBox = document.getElementById('result_compare_texts');
            const text1HighlightBox = document.getElementById('compare_text_1_highlight');
            const text2HighlightBox = document.getElementById('compare_text_2_highlight');
            const spinnerId = 'spinner_compare_texts';

            if (!text1.trim() || !text2.trim()) { alert("请输入两段文本进行比较。"); return; }
            if (!optLiteral && !optSemantic && !optNgram) { alert("请至少选择一种比较类型。"); return; }

            showSpinner(spinnerId, true);
            displayJsonResult('result_compare_texts', null, "正在比较...");
            text1HighlightBox.innerHTML = escapeHtml(text1).replace(/\n/g, '<br>');
            text2HighlightBox.innerHTML = escapeHtml(text2).replace(/\n/g, '<br>');


            const payload = {
                text1: text1,
                text2: text2,
                options: {
                    compare_literal: optLiteral,
                    compare_semantic: optSemantic,
                    compare_ngram: optNgram
                }
            };

            try {
                const response = await fetch(`${plagiarismApiBaseUrl()}/compare_two_texts`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify(payload)
                });
                const data = await response.json();
                if (data.success && data.data) {
                    displayJsonResult('result_compare_texts', data.data, "比较完成，但未返回详细数据。");
                    if (data.data.literal_matches && data.data.literal_matches.length > 0) {
                        highlightComparisonText('compare_text_1_highlight', text1, data.data.literal_matches, true);
                        highlightComparisonText('compare_text_2_highlight', text2, data.data.literal_matches, false);
                    }
                } else {
                    displayJsonResult('result_compare_texts', { error: data.error || "未知错误" });
                }
            } catch (error) {
                console.error("双文本比较请求失败:", error);
                displayJsonResult('result_compare_texts', { error: `请求错误: ${error.message}` });
            } finally {
                showSpinner(spinnerId, false);
            }
        }

        async function runVectorExploration() {
            const text = document.getElementById('vector_text_input').value;
            const resultBox = document.getElementById('result_vector_explore');
            const spinnerId = 'spinner_vector_explore';

            if (!text.trim()) { alert("请输入文本。"); return; }
            showSpinner(spinnerId, true);
            displayJsonResult('result_vector_explore', null, "正在获取向量...");


            try {
                const response = await fetch(`${plagiarismApiBaseUrl()}/get_vector`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ text_content: text })
                });
                const data = await response.json();
                if (data.success && data.data) {
                    let vector_preview = "N/A";
                    if (data.data.vector && Array.isArray(data.data.vector)) {
                        vector_preview = `维度: ${data.data.vector.length}, 向量 (前10维): [${data.data.vector.slice(0, 10).map(v => v.toFixed(4)).join(', ')}...]`;
                    }
                    const displayData = { 
                        vector_preview: vector_preview, 
                        full_vector: (data.data.vector && data.data.vector.length <= 256 ? data.data.vector : "向量过长，未完整显示 (请查看浏览器控制台)") // 限制显示长度
                    };
                    if (data.data.vector && data.data.vector.length > 256) {
                        console.log("完整向量 for " + text.substring(0,20) + ":", data.data.vector);
                    }
                    displayJsonResult('result_vector_explore', displayData, "未能获取向量。");
                } else {
                    displayJsonResult('result_vector_explore', { error: data.error || "未知错误" });
                }
            } catch (error) {
                console.error("向量探索请求失败:", error);
                displayJsonResult('result_vector_explore', { error: `请求错误: ${error.message}` });
            } finally {
                showSpinner(spinnerId, false);
            }
        }

    </script>
</body>

</html>